package com.googlecode.mapperdao

/**
 * The MapperDao is the central trait that allows CRUD operations on entities.
 *
 * insert, update, delete and select's can be performed and all these methods
 * require the entity as a parameter and optionally a configuration for the
 * operation..
 *
 * @author kostantinos.kougios
 */
trait MapperDao
{
	// default configurations, can be overriden
	protected val DefaultSelectConfig = SelectConfig.Default
	protected val DefaultDeleteConfig = DeleteConfig.Default
	protected val DefaultUpdateConfig = UpdateConfig(deleteConfig = DefaultDeleteConfig)

	/**
	 * insert an entity into the database. The entity and all related non-persisted entities
	 * will be inserted into the database. All related persisted entities will be updated
	 * if their state changed.
	 *
	 * @param	entity		the entity, i.e. ProductEntity
	 * @param	o			the value, i.e. a Product
	 * @return	T with PC, i.e. Product with IntId which contains any autogenerated keys.
	 */
	def insert[ID, PC <: Persisted, T](entity: Entity[ID, PC, T], o: T): T with PC = insert(DefaultUpdateConfig, entity, o)

	/**
	 * Will insert the entity into the database and will use the UpdateConfig to decide
	 * which related entities will be inserted, deleted etc.
	 *
	 * @see 	#UpdateConfig for configuration documentation.
	 * @see		#insert(entity,o)
	 */
	def insert[ID, PC <: Persisted, T](updateConfig: UpdateConfig, entity: Entity[ID, PC, T], o: T): T with PC = {
		if (o == null) throw new NullPointerException("o can't be null")
		insertBatch(updateConfig, entity, o :: Nil).head
	}

	/**
	 * batch inserts all values
	 *
	 * @param entity		the entity
	 * @param values		the values for entity
	 * @return				the inserted values along with their ID's (if ids are surrogate)
	 */
	def insertBatch[ID, PC <: Persisted, T](
		entity: Entity[ID, PC, T],
		values: List[T]
		): List[T with PC]

	/**
	 *
	 * @param updateConfig	the UpdateConfig to use for this batch insert
	 * @param entity		the entity
	 * @param values		the values for entity
	 * @return				the inserted values along with their ID's (if ids are surrogate)
	 */
	def insertBatch[ID, PC <: Persisted, T](
		updateConfig: UpdateConfig,
		entity: Entity[ID, PC, T],
		values: List[T]
		): List[T with PC]

	/**
	 * updates a mutable entity. Non-persisted related entities will be inserted and persisted
	 * related entities will be updated (if their state changed).
	 *
	 * @param	entity	The entity, i.e. ProductEntity
	 * @param	o		the modified value, T with PC (persisted). I.e. Product with IntId
	 *
	 * @return	the updated entity, matching o, with type T with PC (hence it will include
	 *            any autogenerated primary keys). I.e. Product with IntId which is equal to o
	 */
	def update[ID, PC <: Persisted, T](entity: Entity[ID, PC, T], o: T with PC): T with PC = update(DefaultUpdateConfig, entity, o)

	/**
	 * configurable update of a mutable entity
	 *
	 * @see 	#UpdateConfig for configuration documentation.
	 * @see		#update(entity,o)
	 */
	def update[ID, PC <: Persisted, T](updateConfig: UpdateConfig, entity: Entity[ID, PC, T], o: T with PC): T with PC = {
		if (o == null) throw new NullPointerException("o can't be null")
		updateBatchMutable(updateConfig, entity, o :: Nil).head
	}

	/**
	 * batch update values, please call this if the entity T is mutable.
	 *
	 * @param entity	the entity to be updated
	 * @param values	the values for the entity. Since the entity T is mutable, the values must have mutated
	 * @return			the updated values.
	 */
	def updateBatchMutable[ID, PC <: Persisted, T](
		entity: Entity[ID, PC, T],
		values: List[T with PC]
		): List[T with PC] = updateBatchMutable(DefaultUpdateConfig, entity, values)

	/**
	 * batch update values, please call this if the entity T is mutable.
	 * @param updateConfig	the config for this update
	 * @param entity		the entity to be updated
	 * @param values		the values for the entity. Since the entity T is mutable, the values must have mutated
	 * @return				the updated values.
	 */
	def updateBatchMutable[ID, PC <: Persisted, T](
		updateConfig: UpdateConfig,
		entity: Entity[ID, PC, T],
		values: List[T with PC]
		): List[T with PC]


	/**
	 * update of an immutable entity.
	 *
	 * @param	entity	The entity, i.e. ProductEntity
	 * @param	o		the old value, T with PC (persisted). I.e. Product with IntId
	 * @param	newO	the new value, T. I.e. Product
	 *
	 * @return	the updated entity, matching T but with type T with PC (hence it will include
	 *            any autogenerated primary keys). I.e. Product with IntId which is equal to newO
	 */
	def update[ID, PC <: Persisted, T](entity: Entity[ID, PC, T], o: T with PC, newO: T): T with PC =
		update(DefaultUpdateConfig, entity, o, newO)

	/**
	 * configurable update of immutable entities. Similar to update(entity,o,newO)
	 * but updateConfig configures the update.
	 *
	 * @see 	#UpdateConfig for configuration documentation.
	 * @see		#update(entity,o,newO)
	 */
	def update[ID, PC <: Persisted, T](
		updateConfig: UpdateConfig,
		entity: Entity[ID, PC, T],
		o: T with PC,
		newO: T
		): T with PC = {
		if (o == null) throw new NullPointerException("o can't be null")
		if (newO == null) throw new NullPointerException("newO can't be null")
		updateBatch(updateConfig, entity, List((o, newO))).head
	}

	/**
	 * this method batch-updates immutable entities.
	 *
	 * The returned list contains the updated entities in the same
	 * order as the values param.
	 *
	 * @param entity	the entity of T to be updated
	 * @param values	the value pairs, (already persisted immutable entity value, modified value)
	 * @return			the updated values
	 */
	def updateBatch[ID, PC <: Persisted, T](
		entity: Entity[ID, PC, T],
		values: List[(T with PC, T)]
		): List[T with PC] = updateBatch(DefaultUpdateConfig, entity, values)

	/**
	 * this method batch-updates immutable entities.
	 *
	 * The returned list contains the updated entities in the same
	 * order as the values param.
	 *
	 * @param updateConfig	the update configuration
	 * @param entity	the entity of T to be updated
	 * @param values	the value pairs, (already persisted immutable entity value, modified value)
	 * @return			the updated values
	 */
	def updateBatch[ID, PC <: Persisted, T](
		updateConfig: UpdateConfig,
		entity: Entity[ID, PC, T],
		values: List[(T with PC, T)]
		): List[T with PC]

	/**
	 * merges o with the database value according to id. If id exists in the database, an update
	 * will be performed (this means that there will be a select before an update). Otherwise,
	 * an insert of o will be done.
	 *
	 * Please note no special rules for the id are taken into account. I.e. if id=-1 the select will
	 * be done and if there is no value then an insert will be performed. So it is adviced if
	 * o is not persisted, to manually insert it, i.e.
	 *
	 * if(id==-1) mapperDao.insert(MyEntity,o) else mapperDao.merge(MyEntity,o,id)
	 *
	 * @param entity		the entity of o
	 * @param o				the entity instance
	 * @param id			the id
	 * @return				the merged instance
	 */
	def merge[ID, PC <: Persisted, T](
		entity: Entity[ID, PC, T],
		o: T,
		id: ID
		): T with PC = merge(DefaultSelectConfig, DefaultUpdateConfig, entity, o, id)

	def merge[ID, PC <: Persisted, T](
		selectConfig: SelectConfig,
		updateConfig: UpdateConfig,
		entity: Entity[ID, PC, T],
		o: T,
		id: ID
		): T with PC


	/**
	 * select an entity by it's ID
	 *
	 * @param entity	the entity, i.e. ProductEntity
	 * @param id		the id that will be fetched
	 * @return			Option[T with PC] or None, i.e. Some(Product with IntId) if the id
	 *                    exists.
	 */
	def select[ID, PC <: Persisted, T](entity: Entity[ID, PC, T], id: ID): Option[T with PC] = select(DefaultSelectConfig, entity, id)

	/**
	 * select an entity with configuration of what will be loaded, lazy loaded, caching etc..
	 * i.e.
	 * SelectConfig(skip=Set(ProductEntity.attributes)) // attributes won't be loaded
	 *
	 * @param	selectConfig	the configuration for this select
	 * @param	entity			the entity to load i.e. ProductEntity
	 * @param	id				the id of the entity
	 * @return	Option[T with PC] i.e. Some(Product with IntId)
	 *
	 * @see 	#SelectConfig for all available configuration parameters
	 */
	def select[ID, PC <: Persisted, T](selectConfig: SelectConfig, entity: Entity[ID, PC, T], id: ID): Option[T with PC]


	/**
	 * deletes an entity from the database. By default, related entities won't be deleted.
	 * It is assumed that the delete will cascade appropriatelly.
	 * Please use delete(deleteConfig, entity, o) to fine tune the operation.
	 *
	 * @param	entity		the entity to be deleted, i.e. ProductEntity
	 * @param	o			the value of the entity that will be deleted, T with PC, i.e. a Product.
	 *                        o should have been retrieved from the database.
	 * @return	the value of the entity unlinked from the database.
	 */
	def delete[ID, PC <: Persisted, T](entity: Entity[ID, PC, T], o: T with PC): T =
		delete(DefaultDeleteConfig, entity, o)

	/**
	 * configurable delete of an entity. This allows fine tuned deletion of the entity
	 * and all related data.
	 *
	 * @param	deleteConfig	the configuration, please see #DeleteConfig
	 * @param	entity			the entity to be deleted, i.e. ProductEntity
	 * @param	o				the value of the entity that will be deleted, T with PC, i.e. a Product.
	 *                            o should have been retrieved from the database.
	 * @return	the value of the entity unlinked from the database.
	 */
	def delete[ID, PC <: Persisted, T](deleteConfig: DeleteConfig, entity: Entity[ID, PC, T], o: T with PC): T

	/**
	 * this will delete an entity based on it's id.
	 *
	 * The delete will cascade to related entities only if there are cascade constraints
	 * on the foreign keys in the database. In order to configure mapperdao to delete
	 * related entities, select() the entity first and then delete it using
	 * delete(deleteConfig, entity, o). (In any case to do the same at the database level,
	 * queries would be required in order to delete the related data)
	 */
	def delete[ID, PC <: Persisted, T](entity: Entity[ID, PC, T], id: ID)

	/**
	 * unlinks an entity from mapperdao. The entity is not tracked for changes and can't
	 * be used in updates or deletes. The extra memory used by mapperdao is released.
	 *
	 * Use this i.e. when you want to store the entity in a session.
	 */
	def unlink[ID, PC <: Persisted, T](entity: Entity[ID, PC, T], o: T): T = throw new IllegalStateException("Not supported")

	def link[ID, PC <: Persisted, T](entity: Entity[ID, PC, T], o: T with PC): T with PC
}
